package intopo.game.impl.ipd;

import java.io.PrintStream;
import org.w3c.dom.Element;
import java.util.Vector;
import cern.jet.random.engine.RandomEngine;
import cern.jet.random.engine.MersenneTwister;

import util.xml.*;
import intopo.game.Game;
import java.util.Scanner;

/**
 * @author <a href="mailto:plsm@ua.pt">Pedro Lopes da Silva Mariano</a>
 *
 * @version 2.0
 */
final public class IteratedPrisonerDilemmaGame
	implements Game<IteratedPrisonerDilemmaStrategy>
{
	static String CONSTRUCTOR_TAG = "IteratedPrisonerDilemmaGame";
	/**
	 * Maximum number of iterations an Iterated Prisoner's Dilemma game
	 * has. Since the history of this game is implemented using static
	 * vectors, a maximum number of iterations had to be selected.
	 */
	public static final int MAX_ITERATIONS = 1000;

	/**
	 * Random number generator used in calculating the probability of
	 * occurring one more game iteration.
	 */
	private static final RandomEngine random = new MersenneTwister (new java.util.Date ());

	/**
	 * Payoff matrix of the Prisoner's Dilemma Game. This field is
	 * initialized to a 2 by 2 matrix. The indexes to this array are the
	 * actions performed by each player. Action cooperate corresponds to
	 * index 1 while defecting corresponds to index 0.
	 */
	private final float payoffMatrix[][] = new float[2][2];

	/**
	 * Probability of occurring one more iteration/round of the game.
	 */
	private final float oneMoreRoundProbability;

	/**
	 * Minimum number of iterations a Give-Take game has.
	 */
	private final int minimumNumberOfIterations;

	/**
	 * <p><b>Pre-Condition</b> The parameter values are valid.
	 *
	 * @param temptation
	 * @param cooperate
	 * @param penalty
	 * @param suckers
	 * @param oneMoreRoundProbability
	 * @param minimumNumberOfIterations
	 */
	public IteratedPrisonerDilemmaGame (float temptation, float cooperate, float penalty, float suckers, float oneMoreRoundProbability, int minimumNumberOfIterations)
	{
		assert temptation > cooperate
			&& cooperate > penalty
			&& penalty > suckers
			&& 2 * cooperate > temptation + suckers
			&& oneMoreRoundProbability >= 0
			&& oneMoreRoundProbability <= 1
			&& minimumNumberOfIterations >= 0;

		payoffMatrix [0][0] = penalty;
		payoffMatrix [0][1] = temptation;
		payoffMatrix [1][0] = suckers;
		payoffMatrix [1][1] = cooperate;
		this.oneMoreRoundProbability = oneMoreRoundProbability;
		this.minimumNumberOfIterations =  minimumNumberOfIterations;
	}
	/**
	 * <p><b>Pre-Condition</b> The values in the scanner are valid.
	 * @param scanner
	 */
	public IteratedPrisonerDilemmaGame (Scanner scanner)
	{
		this (scanner.nextFloat (), scanner.nextFloat (), scanner.nextFloat (), scanner.nextFloat (), scanner.nextFloat (), scanner.nextInt ());
	}
	/**
	 * Construct a new instance whose parameters are taken from the XML
	 * element. This element must have the following attributes:

	 * <ul><li>{@code penalty} a float number that represents the payoff of
	 the players when both play defect</li>

	 * <li>{@code temptation} a float number that represents the payoff of
	 * the player that defected when its partner cooperated.</li>

	 * <li>{@code suckers} a float number that represents the payoff of the
	 * player that cooperated when its parnter defected.</li>

	 * <li>{@code cooperate} a float number that represents the payoff of
	 * the players when both cooperate.</li>

	 * <li>{@code oneMoreRoundProbability} a float number between 0 and 1
	 * that represents the probability of occuring one more round.</li>

	 * <li>{@code minimumNumberOfIterations} a positive integer number that
	 * represents the minimum number of iterations.</li></ul>

	 * <p> An example of part of a XML document is:

	 * <p><pre> &lt;IteratedPrisonerDilemmaGame
    *           penalty="1"
    *           temptation="5"
    *           suckers="0"
    *           cooperate="3"
	 *           oneMoreRoundProbability="0.5"
	 *           minimumNumberOfIterations="10" /&gt;</pre>

	 * @param element The element that represents the XML document.

	 * @throws XMLParseException if any of the specified attributes does not
	 * exist or a parsing error occurred.
	 */
	public IteratedPrisonerDilemmaGame (Element element)
		throws XMLParseException
	{
		AttributeParser<Integer> ap_int = AttributeParser.getInteger ();
		AttributeParser<Double> ap_double = AttributeParser.getDouble ();
		AttributeParser<Double> ap_prob = AttributeParser.get (0.0, 1.0);

		payoffMatrix [0][0] = ap_double.parse (element, "Penalty").floatValue ();
		payoffMatrix [0][1] = ap_double.parse (element, "Temptation").floatValue ();
		payoffMatrix [1][0] = ap_double.parse (element, "Suckers").floatValue ();
		payoffMatrix [1][1] = ap_double.parse (element, "Cooperate").floatValue ();
		oneMoreRoundProbability = ap_prob.parse (element, "OneMoreRoundProbability").floatValue ();
		minimumNumberOfIterations =  ap_int.parse (element, "MinimumNumberOfIterations").intValue ();

		assert payoffMatrix[0][1] > payoffMatrix[1][1]
			&& payoffMatrix[1][1] > payoffMatrix[0][0]
			&& payoffMatrix[0][0] > payoffMatrix[1][0]
			&& 2 * payoffMatrix[1][1] > payoffMatrix[0][1] + payoffMatrix[1][0];
	}
	public float penalty ()
	{
		return payoffMatrix [0][0];
	}
	public float temptation ()
	{
		return payoffMatrix [0][1];
	}
	public float suckers ()
	{
		return payoffMatrix [1][0];
	}
	public float cooperate ()
	{
		return payoffMatrix [1][1];
	}
	public float getOneMoreRoundProbability ()
	{
		return this.oneMoreRoundProbability;
	}
	public int getMinimumNumberOfIterations ()
	{
		return this.minimumNumberOfIterations;
	}
	public int numberPlayers ()
	{
		return 2;
	}

	public void play (Strategies<IteratedPrisonerDilemmaStrategy> players)
		throws IllegalArgumentException
	{
		final boolean action[] = new boolean [2];
		final IteratedPrisonerDilemmaHistory ipdh = new IteratedPrisonerDilemmaHistory ();

		while (ipdh.numberOfIterations < minimumNumberOfIterations
				 || (ipdh.numberOfIterations < MAX_ITERATIONS
					  && random.nextFloat () <= oneMoreRoundProbability)) {

			for (int i = 0; i < 2; i++) {
				action [i] = players.getStrategy (i).cooperate (ipdh, i);
			}
			ipdh.processIteration (action);
		}
		for (int i = 0; i < 2; i++) {
			players.setPayoff (i, ipdh.getPayoff (i));
		}
		
//		ipdh.print (System.out);
	}

	/**
	 * Return the lowest payoff which is equal to the suckers payoff

	 * @return the lowest payoff
	 */
	public float lowestPayoff ()
	{
		return payoffMatrix [1][0]; // suckers
	}

	/**
	 * Return the highest payoff which is equal to the temptation payoff

	 * @return the highest payoff
	 */
	public float highestPayoff ()
	{
		return payoffMatrix [0][1]; // temptation
	}

	public float paretoPayoff ()
	{
		return payoffMatrix [1][1]; // temptation
	}

	@Override
	public void write (PrintStream ps) {
		ps.print (CONSTRUCTOR_TAG);
		ps.print (' ');
		ps.print (this.payoffMatrix [0][1]);
		ps.print (' ');
		ps.print (this.payoffMatrix [1][1]);
		ps.print (' ');
		ps.print (this.payoffMatrix [0][0]);
		ps.print (' ');
		ps.print (this.payoffMatrix [1][0]);
		ps.print (' ');
		ps.print (this.oneMoreRoundProbability);
		ps.print (' ');
		ps.print (this.minimumNumberOfIterations);
	}




	private class IteratedPrisonerDilemmaHistory
		implements intopo.game.impl.ipd.IteratedPrisonerDilemmaHistory
	{
		boolean cooperate[][] = new boolean [MAX_ITERATIONS][2 /*getNumberPlayers ()*/];

		float payoff[] = new float [2 /*getNumberPlayers ()*/];

		int numberOfIterations = 0;

		public boolean cooperatedAtIteration (int iteration, int playerIndex)
		{
			assert iteration >= 0 && iteration < numberOfIterations;
			assert playerIndex >= 0 && playerIndex < 2 /*getNumberPlayers ()*/;

			return cooperate [iteration][playerIndex];
		}

		public boolean cooperatedInPastIteration (int iteration, int playerIndex)
		{
			assert iteration > 0 && iteration <= numberOfIterations;
			assert playerIndex >= 0 && playerIndex < 2 /*getNumberPlayers ()*/;

			return cooperate [numberOfIterations - iteration][playerIndex];
		}

		public int getNumberOfIterations ()
		{
			return numberOfIterations;
		}

		public float getPayoff (int playerIndex)
		{
			return payoff [playerIndex] / numberOfIterations;
		}

		void processIteration (boolean []action)
		{
			for (int i = 0; i < 2 /*getNumberPlayers ()*/; i++) {
				cooperate [numberOfIterations][i] = action [i];
				int first = (action [i] ? 1 : 0);
				int second = (action [1 - i] ? 1 : 0);
				payoff [i] += payoffMatrix [first][second];
			}
			numberOfIterations++;
		}

		public void print (java.io.PrintStream ps)
		{
			for (int j = 0; j < 2; j++) {
				for (int i = 0; i < numberOfIterations; i++)
					ps.print (cooperate [i][j] ? 'C' : 'D');
				ps.println (" " + payoff [j]);
			}
		}
	}

}
